package data;

import java.util.*;

public class Interpolator
{

	private int m_PrecisionDigits;
	private double m_AxialTilt;
	private double m_Eccentricity;
	private double m_GeoTolerance;
	private int m_MinutesTolerance;
	private double m_MaxSpaceError;
	private Database m_Database;

	public Interpolator( double geoTolerance, int minutesTolerance, int precisionDigits, double axialTilt, double eccentricity, Database db )
	{
		m_GeoTolerance = geoTolerance;
		m_MinutesTolerance = minutesTolerance;
		m_PrecisionDigits = precisionDigits;
		m_AxialTilt = axialTilt;
		m_Eccentricity = eccentricity;
		m_Database = db;

		m_MaxSpaceError = Math.sqrt( 2.0 * geoTolerance * geoTolerance );
	}


	public double interpolatedValue( double latitude, double longitude, java.util.GregorianCalendar time ) throws InsufficientDataException
	{
		int i;
		InterpSample[] samples = new InterpSample[8];
		double totalWeight = 0.0;
		double value = 0.0;
		int inError = 0;
		int afterInError = 0;
		// generate samples
		for( i = 0; i < 8; i++ ) {
		    /*
		    samples[i] = chooseMinimumError( latitude, 
					longitude,
					time, 
					search( latitude, 
							longitude, 
							time, 
							(i & 1) != 0, 
							(i & 2) != 0, 
							(i & 4) != 0 ) );
		    */
		    
		    samples[i] = 
			m_Database.getCellSampleFactory().findMinError( latitude,
									longitude,
									time,
									(i & 1) != 0,
									(i & 2) != 0,
									(i & 4) != 0,
									m_GeoTolerance,
									m_MinutesTolerance,
									m_PrecisionDigits,
									m_AxialTilt,
									m_Eccentricity );

		    if( samples[i] == null ) {
		    	if((i & 4) == 0) {
		    		// This is the "after" sample
		    		afterInError++;
		    	}
		    	inError++;
		    } else
		    	totalWeight += samples[i].weight;
		}

		if(inError == 8 || afterInError == 4)
			throw new InsufficientDataException( latitude,
				     longitude,
				     time,
				     m_GeoTolerance,
				     m_MinutesTolerance );
		// blend the samples
		// weight each sample inversely against its error
		for( i = 0; i < 8; i++ ) {
			if(samples[i] != null)
				value += samples[i].sample.value * ( samples[i].weight / totalWeight );
		}

		//System.out.println("Returning " + value + " for " + longitude + ", " + latitude);
		return value;
	}

	private double sampleError( double latitude, double longitude, java.util.GregorianCalendar time, CellSample sample )
	{
		// TODO: write error function here
		long targetTime = time.getTime().getTime();
		long sampleTime = sample.time.getTime();
		double timeError = ( Math.abs( sampleTime - targetTime ) / ( 1000.0 * 60.0 ) ) / ( (double) m_MinutesTolerance );

		double latDist = sample.coord_latitude - latitude;
		double longDist = sample.coord_longitude - longitude;
		double spaceError = Math.sqrt( latDist * latDist + longDist * longDist ) / ( m_MaxSpaceError );

		return (timeError + spaceError) / 2.0;
	}

	private InterpSample chooseMinimumError( double latitude, double longitude, java.util.GregorianCalendar time, LinkedList samples )
	{
		CellSample bestSample = null;
		double bestError = 3.0;

		// compute error for all entries, pick minimum
		ListIterator iter = samples.listIterator();
		while( iter.hasNext() ) {
			CellSample item = (CellSample) iter.next();
			double currentError = sampleError( latitude, longitude, time, item );

			if( bestSample == null || currentError < bestError ) {
				bestSample = item;
				bestError = currentError;
			}
		}

		InterpSample is = new InterpSample();
		is.sample = bestSample;
		is.weight = 1.0 / ( bestError + 0.001 );

		return is;
	}

	private LinkedList search( double latitude, double longitude, java.util.GregorianCalendar time, boolean above, boolean left, boolean before ) throws InsufficientDataException
	{
		String strTime = "'" + (new java.sql.Timestamp( time.getTime().getTime() )).toString() + "'";
		String conditions = "coord_latitude " + (above ? ">=" : "<=") + " " + latitude + 
		" AND coord_longitude " + (left ? ">=" : "<=") + " " + longitude + 
		" AND time " + (before ? "<=" : ">=") + " " + strTime;

		LinkedList results = m_Database.getCellSampleFactory().findInRange( latitude, 
				longitude, 
				time, 
				m_GeoTolerance, 
				m_GeoTolerance, 
				m_MinutesTolerance, 
				m_PrecisionDigits,
				m_AxialTilt,
				m_Eccentricity,
				conditions );
		if( results.size() == 0 ) {
			// Throw exception - we'll have to run a simulation
			throw new InsufficientDataException( latitude, longitude, time, m_GeoTolerance, m_MinutesTolerance );
		}

		return results;
	}

}
